<?php

function resolve_ip($hostname, $version) {
    $type = DNS_A | DNS_CNAME | DNS_AAAA;
    $ns = null;
    $continue = true;
    while ($continue) {
        // we cannot change DNS server here, so no handler for DNS_NS
        $ret = dns_get_record($hostname, $type);
        $continue = false;
        foreach ($ret as $record) {
            switch ($record['type']) {
                case 'A':
                    if ($version == '4') {
                        return $record['ip'];
                    }
                    break;
                case 'AAAA':
                    if ($version == '6') {
                        return $record['ipv6'];
                    }
                    break;
                case 'CNAME':
                    $hostname = $record['target'];
                    $continue = true;
                    break;
            }
        }
    }
    return false;
}

function check_domain($domain) {
    if (!defined('FILTER_VALIDATE_DOMAIN')) {
        return true;
    }
    return filter_var($domain, FILTER_VALIDATE_DOMAIN);
}

function check_ipv4($ip) {
    return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}

function check_ipv6($ip) {
    return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}

function get_default_local_ip($version) {
    switch ($version) {
        case '4': {
            $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
            $ret = @socket_connect($sock, '1.2.4.8', 53);
            break;
        }
        case '6': {
            $sock = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP);
            $ret = @socket_connect($sock, '240c::6666', 53);
            break;
        }
        default:
            return false;
    }
    if ($ret === false) return false;
    socket_getsockname($sock, $name);
    return $name;
}

$myIP = [];
$myIP['4'] = get_default_local_ip('4');
$myIP['6'] = get_default_local_ip('6');
echo('local IPv4: ' . var_export($myIP['4'], true) . "\n");
echo('local IPv6: ' . var_export($myIP['6'], true) . "\n");

function get_interface_local_ip($interface, $version) {
    exec("ip address show dev $interface", $output, $retval);
    if ($retval != 0) return false;

    static $findstr = [
        '4' => 'inet ',
        '6' => 'inet6 ',
    ];
    $find = $findstr[$version];
    $pos0 = strpos($output, $find);
    if ($pos0 === false) return false;
    $pos1 = strpos($output, ' ', $pos0) + 1;
    $pos2 = strpos($output, ' ', $pos1);
    $full = substr($output, $pos1, $pos2 - $pos1 + 1);
    $pos3 = strpos($full, '/');
    $ip = substr($full, 0, $pos3);
    return $ip;
}

function get_ip($hostname, $version) {
    static $flags = [
        '4' => FILTER_FLAG_IPV4,
        '6' => FILTER_FLAG_IPV6,
    ];

    if (!empty($hostname)) {
        if ($hostname[0] == '\'') {
            switch ($hostname) {
                case '\'local':
                    global $myIP;
                    return $myIP[$version];
            }
        } else if (filter_var($hostname, FILTER_VALIDATE_IP, $flags[$version])) {
            return $hostname;
        } else if (check_domain($hostname)) {
            return resolve_ip($hostname, $version);
        }
    }
    return false;
}

$header = <<<EOF
#!/usr/sbin/nft -f

add table ip nat
delete table ip nat
add table ip nat
add chain ip nat PREROUTING { type nat hook prerouting priority -100 ; }
add chain ip nat POSTROUTING { type nat hook postrouting priority 100 ; }

add table ip6 nat6
delete table ip6 nat6
add table ip6 nat6
add chain ip6 nat6 PREROUTING { type nat hook prerouting priority -100 ; }
add chain ip6 nat6 POSTROUTING { type nat hook postrouting priority 100 ; }


EOF;

function rule($proto3, $proto4, $localIP, $localPort, $remoteIP, $remotePort) {
    if (!in_array($proto3, ['4', '6'])) {
        return null;
    }
    if ($proto3 == '4') {
        return <<<EOF
add rule ip nat PREROUTING ${proto4} dport ${localPort} counter dnat to ${remoteIP}:${remotePort}
add rule ip nat POSTROUTING ip daddr ${remoteIP} ${proto4} dport ${remotePort} counter snat to ${localIP}

EOF;
    }
    if ($proto3 == '6') {
        return <<<EOF
add rule ip6 nat6 PREROUTING ${proto4} dport ${localPort} counter dnat to ${remoteIP}:${remotePort}
add rule ip6 nat6 POSTROUTING ip6 daddr ${remoteIP} ${proto4} dport ${remotePort} counter snat to ${localIP}

EOF;
    }
}

$s = $header;

$rules = file_get_contents('./rules.txt');
$lines = explode("\n", $rules);
foreach ($lines as $line) {
    $line = trim($line);
    if (empty($line)) continue;

    if ($line[0] == '#') {
        $s .= '#' . substr($line, 1);
        continue;
    }

    $parts = explode(',', $line);
    if (count($parts) !== 5) {
        echo('unknown line: ' . $line . "\n");
        continue;
    }
    list($p3str, $p4str, $lport, $rport, $rhost) = $parts;

    $p3arr = str_split($p3str);
    foreach ($p3arr as $p3char) {
        switch ($p3char) {
            case '4':
                $p3 = '4';
                $lip = $myIP['4'];
                $rip = get_ip($rhost, '4');
                break;
            case '6':
                $p3 = '6';
                $lip = $myIP['6'];
                $rip = get_ip($rhost, '6');
                if ($rip !== false) {
                    $rip = '[' . $rip . ']';
                }
                break;
            default:
                echo('unknown L3: ' . $p3char . "\n");
                continue 2;
        }
        if ($rip === false) {
            echo('unknown host: ' . $rhost . "\n");
            continue;
        }

        $p4arr = str_split($p4str);
        foreach ($p4arr as $p4char) {
            switch ($p4char) {
                case 't':
                    $p4 = 'tcp';
                    break;
                case 'u':
                    $p4 = 'udp';
                    break;
                default:
                    echo('unknown L4: ' . $p3char . "\n");
                    continue 2;
            }

            if ($lip === false) continue;
            $s .= rule($p3, $p4, $lip, $lport, $rip, $rport);
        }
    }
}

file_put_contents('./nat.nft', $s);
shell_exec('chmod +x ./nat.nft');

//$bin = "/usr/sbin/nft -t ${file}";
